package client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * MacheModel
 * 
 * maintains application states for the client handles requests from the
 * MacheController a MacheModel instance is created for each MacheClient instance
 * 
 */
public class MacheModel {
    private MacheClient client;
    private String userID;
    private MacheController controller;
    private Map<String, Conversation> convs; // convIds to conversation
    private String SPACE = " ";

    /**
     * Constructor for MacheModel create a client to associate with this
     * MacheModel
     * 
     * @throws Exception
     */
    public MacheModel(MacheController controller) {
        this.controller = controller;
        this.convs = new HashMap<String, Conversation>();

    }

    /**
     * Called by the MacheController when a new message is sent by the user.
     * Updates local conversation history, tells MacheController to update GUI
     * and sends message to server.
     * 
     * @param message
     * @param convID
     */
    public void newMessage(String font, Message message, String convID) {
        // send message and convID to server
        String input = userID + SPACE + "send" + SPACE + convID + SPACE + font
                + SPACE + message.getMessage();
        client.sendRequest(input);
    }

    /**
     * Called by the MacheController when a new conversation is started by the
     * user. Informs server that a new conversation has been started.
     * 
     * @param friends
     */
    public void newConv(List<String> friends) {
        String input = userID + SPACE + "start";
        for (int i = 0; i < friends.size(); i++) {
            input += SPACE;
            input += friends.get(i);
        }
        client.sendRequest(input);
    }

    /**
     * Called by the MacheController when a user invites new friends to an
     * existing conversation. Informs server to add invitees to the
     * conversation.
     * 
     * @param convID
     * @param friends
     * @return
     */
    public void newInvites(String convID, List<String> friends) {
        String input = userID + SPACE + "invite" + SPACE + convID;
        for (int i = 0; i < friends.size(); i++) {
            input += SPACE;
            input += friends.get(i);
        }
        client.sendRequest(input);

    }

    /**
     * Send request to get a list of active users
     * 
     * @param convID
     * @return
     */
    public void getUsers(String convID) {
        String input = userID + SPACE + "get" + SPACE + convID;
        client.sendRequest(input);
    }

    /**
     * Called by the MacheController when a user chooses to leave a conversation.
     * Informs server to remove this user from the conversation so the user will
     * no longer get updates from this conversation.
     * 
     * @param convID
     * @return
     */
    public void endConv(String convID) {
        String input = userID + SPACE + "end" + SPACE + convID;
        convs.remove(convID);
        client.sendRequest(input);

    }

    /**
     * Called by MacheController to get the list of all active users currently
     * online
     */
    public void getActiveList() {
        String input = userID + SPACE + "active";
        client.sendRequest(input);
    }

    /**
     * Get users that are active in the conversation
     * 
     * @param convID
     */
    public void getActiveListWin(String convID) {
        String input = userID + SPACE + "activewin" + " " + convID;
        client.sendRequest(input);
    }

    /**
     * Called by MacheController to update the typing status of a particular user
     * in a specific conversation
     * 
     * @param status
     * @param convID
     */
    public void updateStatus(String status, String convID) {
        String input = userID + SPACE + "typestatus" + SPACE + convID + SPACE
                + status;
        client.sendRequest(input);
    }

    /**
     * Called by MacheController to nudge a particular user in a specific
     * conversation.
     * 
     * @param friend
     * @param convID
     */
    public void nudge(String friend, String convID) {
        if (convs.get(convID).getUsers().contains(friend)) {
            String input = userID + SPACE + "nudge" + SPACE + convID + SPACE
                    + friend;
            client.sendRequest(input);
        }

    }

    /**
     * Updates Server that the client has exited the application
     */
    public void quit() {
        String input = userID + SPACE + "bye";
        client.sendRequest(input);

    }

    /**
     * Adds a new message that was received from the server to a specific
     * conversation
     * 
     * @param convID
     * @param userID
     * @param message
     */
    public void messageReceived(String convID, String userID, String font,
            String message) {
        Message newMessage = new Message(userID, message);
        // adds newMesage to local conversation history
        if (convs.containsKey(convID)) {
            convs.get(convID).getHistory().add(newMessage);
        } else {
            List<Message> newHistory = new ArrayList<Message>();
            newHistory.add(new Message(userID, message));
            convs.put(convID,
                    Conversation.createConversation(convID, newHistory));
            controller.addConv(convID, new ArrayList<String>());
            // fire controller to create new MacheWindowGUI.
        }
        // fire controller to update GUI
        controller.messageReceived(convID, font, newMessage);
    }

    /**
     * Updates user list of a specific conversation, using the whole set of
     * users. If the conversation is not in the MacheModel, it will do nothing
     * 
     * @param convID
     * @param users
     *            (the whole set of users in the conversation with convID)
     */
    public void usersInConv(String convID, List<String> users) {
        // update user list
        Conversation oldConv = convs.get(convID);
        if (oldConv != null) {
            oldConv.setUsers(users);
            // fire controller to update MacheWindowGUI
            controller.usersInConv(convID, users);
        }
    }
    
    /**
     * Show a message in the GUIs that new people has joined the conversation and who they are.
     * @param convID
     * @param usersArr
     */
    public void newJoinedFriends(String convID, List<String> usersArr) {
        Conversation oldConv = convs.get(convID);
        @SuppressWarnings("unchecked")
        ArrayList<String> copy = (ArrayList<String>) ((ArrayList<String>)usersArr).clone();
        if (oldConv!= null){
            for (String user:usersArr){
                if (oldConv.getUsers().contains(user)){
                    copy.remove(copy.indexOf(user));
                }
            }
            if(copy.contains(userID)){
                copy.remove(copy.indexOf(userID));
            }
            controller.newJoinedFriends(convID, copy);
        }
        
    }

    /**
     * Gives the user who started the conversation the new conversation ID
     * generated by the server
     * 
     * @param convID
     */
    public void addConv(String convID, ArrayList<String> userList) {
        // adds new conversationID to local history
        Conversation newConv = new Conversation(convID);
        newConv.setUsers(userList);

        convs.put(convID, newConv);
        // fire controller to open new MacheWindowGUI
        controller.addConv(convID, userList);

    }

    /**
     * Removes a friend who has left a conversation from that conversation's
     * list of users.
     * 
     * @param convID
     * @param friend
     */
    public void friendLeft(String convID, String friend) {
        // removes friend from list of users in local conversation history
        if (convs.containsKey(convID)){
            if (convs.get(convID).getUsers().contains(friend)) {
                convs.get(convID).getUsers().remove(friend);
                // fire controller to display " <friend> has left conversation"
                // message
                controller.friendLeft(convID, friend);
            }
        }

    }

    /**
     * Removes a friend who has logged off from the user list of all shared
     * conversations.
     * 
     * @param friend
     * 
     */
    public void friendLogOff(String friend) {

        // removes friend from list of users in local conversation history
        Set<String> keys = convs.keySet();
        for (String k : keys) {
            if (convs.get(k).getUsers().contains(friend)) {
                convs.get(k).getUsers().remove(friend);
                // fire controller to display " <friend< has gone offline"
                // message
                controller.friendLogOff(friend, k);
            }
        }
    }

    /**
     * Receives the activeList of users from the server and fires the controller
     * to feed it to the GUIs.
     * 
     * @param usersArr
     *            the list of active users
     */
    public void activeList(List<String> usersArr) {
        controller.giveActiveList(usersArr);
        Set<String> keys = convs.keySet();
        for (String convID : keys) {
            controller.giveActiveListWin(usersArr, convID);
        }
    }

    /**
     * Receives the activeList of users from the server and fires the controller
     * to feed it to the MacheWindowGUI.
     * 
     * @param usersArr
     *            the list of active users in the conversation
     * @param convID
     */
    public void activeListWin(List<String> usersArr, String convID) {
        controller.giveActiveListWin(usersArr, convID);
    }

    /**
     * Receives typing status updates for users in a specific conversation.
     * Calls controller to update MacheWindowGUI.
     * 
     * @param convID
     * @param username
     * @param status
     */
    public void typeStatusUpdate(String convID, String username, String status) {
        if (convs.containsKey(convID)) {
            controller.typeStatusUpdate(convID, username, status);
        }
    }

    /**
     * Receives nudge from
     * 
     * @param convID
     * @param friend
     */
    public void youGotNudged(String convID, String sender) {
        if (convs.containsKey(convID)) {
            controller.youGotNudged(convID, sender);
        }
    }

    /**
     * Set the userid associated with this MacheModel
     * 
     * @param userid
     */
    public void setUserID(String userID) {
        this.userID = userID;
    }

    /**
     * Set the client associated with this MacheModel
     * 
     * @param userid
     */
    public void setClient(MacheClient client) {
        this.client = client;
    }

    
    /**
     * method should only be used in JUnit Tests to get the conversation map.
     * 
     * @return convs
     */
    protected Map<String, Conversation> getConvs() {
        return convs;
    }

    
    /**
     * method should only be used in JUnit Tests to get the userID associated with this model
     * @return the userID associated with this MacheModel
     */
    protected String getUserID() {
        return userID;
    }

    /**
     * method should only be used in JUnit Tests to get the client associated with this model
     * @return MacheClient associated with this MacheModel
     */
    protected MacheClient getClient(){
        return client;
    }
    
}